/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.skatiger.jrove.impl;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;

import javax.el.ELException;

import org.skatiger.jrove.Configurator;
import org.skatiger.jrove.Jrove;
import org.skatiger.jrove.JroveException;
import org.skatiger.jrove.JroveFactory;
import org.skatiger.jrove.JroveHandler;
import org.skatiger.jrove.ResourceResolver;
import org.skatiger.jrove.compiler.Compiler;
import org.skatiger.jrove.config.ConfigProperties;
import org.skatiger.jrove.util.AssertUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class DefaultJroveFactory extends JroveFactory {

    protected final static Logger logger = LoggerFactory
            .getLogger(DefaultJroveFactory.class);
    public static final long DEFAULT_REFRESH_PERIOD = 2;
    private final Compiler _compiler;

    private Map<String, Jrove> _jroves;

    private Map<String, URL> _relativeLocations;
    private final ResourceResolver _resolver;

    private final URL _baseUrl;

    private final long _refreshPeriod;

    public DefaultJroveFactory(Compiler _compiler, Configurator _configurator) {
        AssertUtil.notNull(_compiler);
        AssertUtil.notNull(_configurator);

        this._compiler = _compiler;
        this._jroves = new HashMap<String, Jrove>();
        this._relativeLocations = new HashMap<String, URL>();
        this._resolver = _configurator.getResourceResolver();
        this._baseUrl = _resolver.resolveUrl("/");

        // init _refreshPeriod
        long __refreshPeriod = _configurator
                .getProperty(ConfigProperties.REFRESH_PERIOD);
        this._refreshPeriod = (__refreshPeriod >= 0) ? __refreshPeriod * 1000
                : DEFAULT_REFRESH_PERIOD;
        if (logger.isInfoEnabled()) {
            logger.info("Using Refresh Period: " + this._refreshPeriod);
        }
    }

    private DefaultJrove createJrove(URL url) throws IOException,
            JroveException,
            ELException {
        logger.debug("Creating Jrovelet for: " + url);
        String alias = "/"
                + url.getFile().replaceFirst(this._baseUrl.getFile(), "");
        try {
            JroveHandler h = this._compiler.compile(url, alias);
            DefaultJrove f = new DefaultJrove(this, url, alias, h);
            return f;
        } catch (FileNotFoundException fnfe) {
            throw new FileNotFoundException("Jrovelet " + alias
                    + " not found at: " + url.toExternalForm());
        }
    }

    @Override
    public Jrove getJrove(String uri) throws IOException,
            JroveException,
            ELException {
        URL url = this._relativeLocations.get(uri);
        if (url == null) {
            url = this.resolveURL(this._baseUrl, uri);
            if (url != null) {
                Map<String, URL> newLoc = new HashMap<String, URL>(
                        this._relativeLocations);
                newLoc.put(uri, url);
                this._relativeLocations = newLoc;
            } else {
                throw new IOException("'" + uri + "' not found.");
            }
        }
        return this.getJrove(url);
    }

    public Jrove getJrove(URL url) throws IOException,
            JroveException,
            ELException {
        AssertUtil.notNull(url);
        String key = url.toString();
        DefaultJrove f = (DefaultJrove) this._jroves.get(key);
        if (f == null || this.needsToBeRefreshed(f)) {
            f = this.createJrove(url);
            if (this._refreshPeriod != 0) {
                Map<String, Jrove> newLoc = new HashMap<String, Jrove>(
                        this._jroves);
                newLoc.put(key, f);
                this._jroves = newLoc;
            }
        }
        return f;
    }

    public long getRefreshPeriod() {
        return _refreshPeriod;
    }

    private boolean needsToBeRefreshed(DefaultJrove jrove) {
        // if set to 0, constantly reload-- nocache
        if (this._refreshPeriod == 0) {
            return true;
        }
        // if set to -1, never reload
        if (this._refreshPeriod == -1) {
            return false;
        }
        long ttl = jrove.getCreateTime() + this._refreshPeriod;
        if (System.currentTimeMillis() > ttl) {
            try {
                URL source = jrove.getSource();
                URLConnection conn = source.openConnection();
                InputStream is = conn.getInputStream();
                try {
                    long atl = conn.getLastModified();
                    return atl == 0 || atl > ttl;
                } finally {
                    if (is != null) {
                        try {
                            is.close();
                        } catch (Exception e) {
                            // do nothing
                        }
                    }
                }
            } catch (Exception e) {
                throw new JroveException("Error Checking Last Modified for "
                        + jrove.getAlias(), e);
            }
        }
        return false;
    }

    URL resolveURL(URL source, String path) throws IOException {
        if (path.startsWith("/")) {
            URL url = this._resolver.resolveUrl(path);
            if (url == null) {
                throw new FileNotFoundException(path
                        + " Not Found in ResourceResolver");
            }
            return url;
        } else {
            return new URL(source, path);
        }
    }
}
